/*
    libnbds
    Non-blocking Data Structures Library

    Copyright (C) 2011 Paweł Dziepak

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef NBDS_ATOMIC_H
#define NBDS_ATOMIC_H

#include "mem_barrier.h"
#include "types.h"

namespace nbds {
	namespace arch {
		/* Low level functions */
		u64 cmp_and_swp_64(u64*, u64, u64);
		u32 cmp_and_swp_32(u32*, u32, u32);
		u16 cmp_and_swp_16(u16*, u16, u16);
		u8 cmp_and_swp_8(u8*, u8, u8);

		u64 ftch_and_add_64(u64*, u64);
		u32 ftch_and_add_32(u32*, u32);
		u16 ftch_and_add_16(u16*, u16);
		u8 ftch_and_add_8(u8*, u8);

		u64 xchg_64(u64*, u64);
		u32 xchg_32(u32*, u32);
		u16 xchg_16(u16*, u16);
		u8 xchg_8(u8*, u8);

		void cmp_and_swp2_64(u64*, const u64*, const u64*, u64*);
		void cmp_and_swp2_32(u32*, const u32*, const u32*, u32*);
		void cmp_and_swp2_16(u16*, const u16*, const u16*, u16*);
		void cmp_and_swp2_8(u8*, const u8*, const u8*, u8*);

		template<int Size, typename T>
		class atomic {
		public:
			static inline T cmp_and_swp_(T *dst, T cmp, T src);
			static inline T ftch_and_add_(T *dst, T val);
			static inline T xchg_(T *dst, T val);

			static inline void cmp_and_swp2_(T *dst, const T *cmp,
							const T *src, T *res);
		};

		/* Interface */
		template<typename T>
		static inline T get(T *src) {
			compiler_barrier();
			T val = *src;
			load_fence();
			return val;
		}

		template<typename T>
		static inline void set(T *dst, T val) {
			store_fence();
			*dst = val;
			compiler_barrier();
		}

		template<typename T>
		static inline T cmp_and_swp(T *dst, T cmp, T src) {
			store_fence();
			T val = atomic<sizeof(T), T>::cmp_and_swp_(dst, cmp,
									src);
			load_fence();
			return val;
		}

		template<typename T>
		static inline T ftch_and_add(T *dst, T val) {
			store_fence();
			T nval = atomic<sizeof(T), T>::ftch_and_add_(dst, val);
			load_fence();
			return nval;
		}

		template<typename T>
		static inline T xchg(T *dst, T val) {
			store_fence();
			T nval = atomic<sizeof(T), T>::xchg_(dst, val);
			load_fence();
			return nval;
		}

		template<typename T>
		static inline void cmp_and_swp2(T *dst, const T *cmp,
							const T *src, T *res) {
			store_fence();
			atomic<sizeof(T), T>::cmp_and_swp2_(dst, cmp, src, res);
			load_fence();
		}		

		/* Partial template specializations */
		template<typename T>
		class atomic<8, T> {
		public:
			static T cmp_and_swp_(T *dst, T cmp, T src) {
				return (T)cmp_and_swp_64((u64*)dst,
							(u64)cmp,
							(u64)src);
			}

			static T ftch_and_add_(T *dst, T val) {
				return (T)ftch_and_add_64((u64*)dst, (u64)val);
			}

			static T xchg_(T *dst, T val) {
				return (T)xchg_64((u64*)dst, (u64)val);
			}

			static void cmp_and_swp2_(T *dst, const T *cmp, 
							const T *src, T *res) {
				cmp_and_swp2_64((u64*)dst, (const u64*)cmp,
						(const u64*)src, (u64*)res);
			}
		};

		template<typename T>
		class atomic<4, T> {
		public:
			static T cmp_and_swp_(T *dst, T cmp, T src) {
				return (T)cmp_and_swp_32((u32*)dst,
							(u32)cmp,
							(u32)src);
			}

			static T ftch_and_add_(T *dst, T val) {
				return (T)ftch_and_add_32((u32*)dst, (u32)val);
			}

			static T xchg_(T *dst, T val) {
				return (T)xchg_32((u32*)dst, (u32)val);
			}

			static T cmp_and_swp2_(T *dst, const T *cmp, 
							const T *src, T *res) {
				cmp_and_swp2_32((u32*)dst, (const u32*)cmp,
						(const u32*)src, (u32*)res);
			}
		};


		template<typename T>
		class atomic<2, T> {
		public:
			static T cmp_and_swp_(T *dst, T cmp, T src) {
				return (T)cmp_and_swp_16((u16*)dst,
							(u16)cmp,
							(u16)src);
			}

			static T ftch_and_add_(T *dst, T val) {
				return (T)ftch_and_add_16((u16*)dst, (u16)val);
			}


			static T xchg_(T *dst, T val) {
				return (T)xchg_16((u16*)dst, (u16)val);
			}

			static T cmp_and_swp2_(T *dst, const T *cmp, 
							const T *src, T *res) {
				cmp_and_swp2_16((u16*)dst, (const u16*)cmp,
						(const u16*)src, (u16*)res);
			}
		};


		template<typename T>
		class atomic<1, T> {
		public:
			static T cmp_and_swp_(T *dst, T cmp, T src) {
				return (T)cmp_and_swp_8((u8*)dst,
							(u8)cmp,
							(u8)src);
			}

			static T ftch_and_add_(T *dst, T val) {
				return (T)ftch_and_add_8((u8*)dst, (u8)val);
			}


			static T xchg_(T *dst, T val) {
				return (T)xchg_8((u8*)dst, (u8)val);
			}

			static T cmp_and_swp2_(T *dst, const T *cmp, 
							const T *src, T *res) {
				cmp_and_swp2_8((u8*)dst, (const u8*)cmp,
						(const u8*)src,	(u8*)res);
			}
		};
	}
}

#endif
